package models

import (
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"github.com/EDDYCJY/go-gin-example/pkg/e"
	"github.com/EDDYCJY/go-gin-example/pkg/setting"
	"github.com/EDDYCJY/go-gin-example/pkg/util"
	"github.com/jinzhu/gorm"
)

type RentOrder struct {
	// 订单记录id
	ID uint64 `gorm:"primary_key"`
	// 租用人的用户id
	UserId uint64 `gorm:"column:user_id"`
	// 机器提交记录id
	SubmitMachineRecordId uint64 `gorm:"column:submit_machine_record_id"`
	//租用类型
	RentType uint32 `gorm:"rent_type"`
	// 虚拟机id
	TaskId string `gorm:"column:task_id"` //all,表示整机，如果ren_type是爱捷云的，则表示是租用的爱捷云租用机器后的InstanceId
	// 租用开始时间
	RentStartTime DbTime `gorm:"column:rent_start_time"`
	// 租用结束时间
	RentEndTime DbTime `gorm:"column:rent_end_time"`
	// 更新时间
	UpdateTime DbTime `gorm:"column:update_time"`
	AutoRenew  bool   `gorm:"column:auto_renew;default:false"` // 是否自动续费
}

const RentTypeAjiyun uint32 = 13

func (RentOrder) TableName() string {
	return "rent_orders"
}

// 创建租用订单
type CreateRentOrderParams struct {
	UserID   uint64
	RecordID uint64
	RentType uint32
	Days     uint32
	Hours    uint32
	// 虚拟机
	VmImageName string
	VmOS        string
	VmBiosMode  string
	VmSshPort   uint32
	VmRdpPort   uint32
	VmVncPort   uint32
	VmCpuCores  uint32
	VmMemSize   uint64
	VmGpuCount  uint32
	VmDiskSize  uint64
}

type RentVmRsp struct {
	CreateTime string `json:"create_time"`
	Desc       string `json:"desc"`
	Status     string `json:"status"`
	TaskID     string `json:"task_id"`
}

func CreateRentOrder(params *CreateRentOrderParams) (*RentVmRsp, e.FResult) {
	user_account, ferror := GetAccountByUserId(params.UserID)
	if ferror.ErrCode != e.SUCCESS {
		return nil, e.FResult{ErrCode: ferror.ErrCode, ErrMsg: ferror.ErrMsg}
	}

	params_wallet_sign, ferror := util.GenWalletSign(user_account.WalletAddr, user_account.PrivateKey)
	if ferror.ErrCode != e.SUCCESS {
		return nil, e.FResult{ErrCode: ferror.ErrCode, ErrMsg: ferror.ErrMsg}
	}

	var submit_machine SubmitMachineRecord
	err := db.Model(&SubmitMachineRecord{}).Debug().
		Where("id = ?", params.RecordID).Find(&submit_machine).Error
	if err != nil {
		return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "机器不存在"}
	}

	// 判断机器的状态是否可以租用
	var data_time struct {
		OnlineTime  DbTime
		OfflineTime DbTime
	}
	err = db.Model(&SubmitMachineRecord{}).Debug().Select("online_time, offline_time").
		Where("id = ?", params.RecordID).
		Scan(&data_time).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "机器不存在"}
		} else {
			return nil, e.FResult_ERROR
		}
	}
	// 当前机器已下线
	var t_now = time.Now().UTC()
	if t_now.Before(time.Time(data_time.OnlineTime)) ||
		(t_now.After(time.Time(data_time.OfflineTime)) || t_now.Equal(time.Time(data_time.OfflineTime))) {
		return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "当前机器已下线"}
	}

	// 当前机器是否已被租用
	if params.RentType == 1 { // 整机
		var orders []RentOrder
		err = db.Model(&RentOrder{}).Debug().
			Where("submit_machine_record_id = ?", params.RecordID).
			Where("rent_start_time <= ? && rent_end_time > ?", t_now, t_now).
			Scan(&orders).Error

		if len(orders) > 0 {
			return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "当前机器已被租用"}
		}
	} else if params.RentType == 2 { //虚拟机
		var orders []RentOrder
		var t_now = time.Now().UTC()
		err = db.Model(&RentOrder{}).Debug().
			Where("submit_machine_record_id = ?", params.RecordID).
			Where("rent_start_time <= ? && rent_end_time > ?", t_now, t_now).
			Scan(&orders).Error
		if len(orders) > 0 {
			//判断剩余的gpu是否够
			var enough bool = true
			var used_cpu_cores uint32 = 0
			var used_mem_size uint64 = 0
			var used_disk_size uint64 = 0
			var used_gpu_count uint32 = 0
			for _, order := range orders {
				if order.RentType == 1 {
					enough = false
					break
				}

				var vm_item VmModify
				err := db.Model(&VmModify{}).Debug().
					Where("rent_order_id = ?", order.ID).
					Order("id desc").First(&vm_item).Error
				if err == nil {
					used_cpu_cores += vm_item.CpuCores
					used_mem_size += vm_item.MemSize
					used_disk_size += vm_item.DiskSize
					used_gpu_count += vm_item.GpuCount
				}
			}

			if !enough {
				return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "当前机器没有足够的资源可供租用"}
			} else {
				if (submit_machine.GpuCount-used_gpu_count) < params.VmGpuCount ||
					(submit_machine.MemSize-used_mem_size) < params.VmMemSize ||
					(submit_machine.DiskSize-used_disk_size) < params.VmDiskSize ||
					(submit_machine.CpuCores-used_cpu_cores) < params.VmCpuCores {
					return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "当前机器没有足够的资源可供租用"}
				}
			}
		}
	}

	// 检查用户的积分是否足够
	var need_pay_coins = setting.CloudPlatformSetting.DayRentCoins / float64(24.0) * float64(params.Days*24+params.Hours)
	if user_account.Coins < need_pay_coins {
		return nil, e.FResult{ErrCode: e.ERROR, ErrMsg: "剩余积分不足"}
	}

	var rentrsp RentVmRsp

	db := db.Begin()
	// 写入租用订单
	var rent_order RentOrder
	rent_order.UserId = params.UserID
	rent_order.SubmitMachineRecordId = params.RecordID
	rent_order.RentType = params.RentType
	if rent_order.RentType == 1 {
		rent_order.TaskId = "all"
	} else if rent_order.RentType == 2 {
		//请求client_node，创建虚拟机，并返回task_id
		api_create_task := fmt.Sprintf("/api/v1/task/create?node_id=%v", submit_machine.NodeId)

		params_request := map[string]interface{}{
			"image_name": params.VmImageName,
			"os":         params.VmOS,
			"bios_mode":  params.VmBiosMode,
			"ssh_port":   strconv.Itoa(int(params.VmSshPort)),
			"rdp_port":   strconv.Itoa(int(params.VmRdpPort)),
			"vnc_port":   strconv.Itoa(int(params.VmVncPort)),
			"cpu_cores":  strconv.Itoa(int(params.VmCpuCores)),
			"mem_size":   strconv.Itoa(int(params.VmMemSize / 1024)),
			"gpu_count":  strconv.Itoa(int(params.VmGpuCount)),
			"disk_size":  strconv.Itoa(int(params.VmDiskSize)),
		}
		for k, v := range params_wallet_sign {
			params_request[k] = v
		}
		post_body, _ := json.Marshal(params_request)

		type CreateRsp struct {
			CreateTime string `json:"create_time"`
			Desc       string `json:"desc"`
			Status     string `json:"status"`
			TaskID     string `json:"task_id"`
		}
		rsp_data := util.PostRequest(api_create_task, post_body)
		if rsp_data.Errcode != 0 {
			return nil, e.FResult{ErrCode: rsp_data.Errcode, ErrMsg: rsp_data.Errmsg}
		}
		resByre, _ := json.Marshal(rsp_data.Data)
		json.Unmarshal(resByre, &rentrsp)

		rent_order.TaskId = rentrsp.TaskID
	}
	t_start := time.Now().UTC()
	rent_order.RentStartTime = DbTime(t_start)
	var total_hours uint32 = params.Days*24 + params.Hours
	t_end := t_start.Add(time.Hour * time.Duration(total_hours))
	rent_order.RentEndTime = DbTime(t_end)
	err = db.Debug().Select("user_id", "submit_machine_record_id", "rent_type", "task_id",
		"rent_start_time", "rent_end_time").Create(&rent_order).Error
	if err != nil {
		if rent_order.RentType == 2 {
			api_delete_task := fmt.Sprintf("/api/v1/task/delete?node_id=%v&task_id=%v",
				submit_machine.NodeId, rent_order.TaskId)
			params_request, _ := json.Marshal(params_wallet_sign)
			util.PostRequest(api_delete_task, params_request)
		}

		db.Rollback()
		return nil, e.FResult_ERROR
	}

	// 虚拟机：写入虚拟机配置变更表
	var vm_modify_item VmModify
	if params.RentType == 2 {
		vm_modify_item.UserID = params.UserID
		vm_modify_item.RentOrderID = rent_order.ID
		vm_modify_item.CpuCores = params.VmCpuCores
		vm_modify_item.GpuCount = params.VmGpuCount
		vm_modify_item.MemSize = params.VmMemSize
		vm_modify_item.DiskSize = params.VmDiskSize

		err = db.Model(&VmModify{}).Debug().
			Select("user_id", "rent_order_id", "cpu_cores", "mem_size", "disk_size", "gpu_count").
			Create(&vm_modify_item).Error
		if err != nil {
			db.Rollback()
			return nil, e.FResult_ERROR
		}
	}

	// 扣除：租用者积分
	err = db.Model(&Account{}).Debug().
		Where("id = ?", rent_order.UserId).
		Update("coins", gorm.Expr("coins - ?", need_pay_coins)).Error
	if err != nil {
		db.Rollback()
		return nil, e.FResult_ERROR
	}

	// 增加：机器所有者积分
	err = db.Model(&Account{}).Debug().
		Where("id = ?", submit_machine.OwnerUserId).
		Update("coins", gorm.Expr("coins + ?", need_pay_coins)).Error
	if err != nil {
		db.Rollback()
		return nil, e.FResult_ERROR
	}

	// 写入支付记录
	var payment_record = PaymentRecord{}
	payment_record.UserId = params.UserID
	if params.RentType == 1 {
		payment_record.PaymentType = 1
		payment_record.RecordId = rent_order.ID
		// todo: 根据公式计算日租金
		payment_record.DayRentCoins = setting.CloudPlatformSetting.DayRentCoins
	} else if params.RentType == 2 {
		payment_record.PaymentType = 3
		payment_record.RecordId = vm_modify_item.ID
		// todo: 根据公式计算日租金
		payment_record.DayRentCoins = setting.CloudPlatformSetting.OneCardDayRentCoins * float64(params.VmGpuCount)
	}
	payment_record.RentPeriod = uint64(total_hours * 3600)
	payment_record.PaymentCoins = payment_record.DayRentCoins / float64(24.0) * float64(total_hours)
	payment_record.PaymentTime = DbTime(t_start)

	err = db.Debug().Select("user_id", "payment_type", "record_id", "rent_period",
		"day_rent_coins", "payment_coins", "payment_time").Create(&payment_record).Error
	if err != nil {
		db.Rollback()
		return nil, e.FResult_ERROR
	}

	db.Commit()
	return &rentrsp, e.FResult_OK
}

type RentOrderInfo struct {
	OrderInfo         RentOrder
	MachineInfo       SubmitMachineRecord
	VmInfo            VmModify
	TotalPaymentCoins float64
}

// 获取用户的所有租用订单列表
func GetRentOrdersByUserId(userId uint64, page uint32, limit uint32) ([]RentOrderInfo, e.FResult) {
	var offset uint32
	if page <= 1 {
		offset = 0
	} else {
		offset = (page - 1) * limit
	}

	var datas []RentOrderInfo

	type tmpOrderMachines struct {
		SubmitMachineRecord
		RentOrder
	}
	var order_machines []tmpOrderMachines
	db.Model(&RentOrder{}).Debug().Select("submit_machines.*, rent_orders.*").
		Joins("left join submit_machines on submit_machines.id = rent_orders.submit_machine_record_id").
		Where("rent_orders.user_id = ?", userId).
		Order("rent_orders.rent_start_time desc").
		Limit(limit).Offset(offset).Scan(&order_machines)

	for _, v := range order_machines {
		var data RentOrderInfo
		data.MachineInfo = v.SubmitMachineRecord
		data.OrderInfo = v.RentOrder

		if data.OrderInfo.TaskId == "all" { //整机
			type tmpSumCoins struct {
				TotalPaymentCoins float64
			}
			var sum_coins tmpSumCoins
			db.Model(&PaymentRecord{}).Debug().
				Select("sum(payment_coins) as total_payment_coins").
				Where("(payment_type = '1' or payment_type = '2') and record_id = ?", data.OrderInfo.ID).
				Scan(&sum_coins)

			data.TotalPaymentCoins = sum_coins.TotalPaymentCoins

			datas = append(datas, data)
		} else { //虚拟机
			var vminfo VmModify
			db.Model(&VmModify{}).Debug().
				Where("rent_order_id = ?", data.OrderInfo.ID).
				Order("id desc").First(&vminfo)
			data.VmInfo = vminfo

			type tmpSumCoins struct {
				TotalPaymentCoins float64
			}

			var sum_coins1 tmpSumCoins
			db.Model(&PaymentRecord{}).Debug().
				Select("sum(payment_coins) as total_payment_coins").
				Where("payment_type = '3'").Or("payment_type = '5'").
				Where("record_id = ?", data.VmInfo.ID).
				Scan(&sum_coins1)
			data.TotalPaymentCoins += sum_coins1.TotalPaymentCoins

			var sum_coins2 tmpSumCoins
			db.Model(&PaymentRecord{}).Debug().
				Select("sum(payment_coins) as total_payment_coins").
				Where("(payment_type = '4') and record_id = ?", data.OrderInfo.ID).
				Scan(&sum_coins2)
			data.TotalPaymentCoins += sum_coins2.TotalPaymentCoins

			datas = append(datas, data)
		}
	}

	return datas, e.FResult_OK
}

// 用户已租用订单的总数量
func GetRentOrdersCountByUserId(userId uint64) (int64, e.FResult) {
	var c int64
	db.Model(&RentOrder{}).Debug().Where("user_id = ?", userId).
		Count(&c)

	return c, e.FResult_OK
}

// 获取机器当前正在被租用的订单列表
func GetRentOrdersByRecordId(submit_machine_recordId uint64) ([]RentOrder, e.FResult) {
	var list []RentOrder
	var t_now = time.Now().UTC()
	err := db.Model(&RentOrder{}).Debug().
		Where("submit_machine_record_id = ? and rent_start_time <= ? and rent_end_time > ?",
			submit_machine_recordId, t_now, t_now).
		Find(&list).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, e.FResult_ERROR
	}
	return list, e.FResult_OK
}

func GetRentOrderByID(id uint64) (*RentOrder, e.FResult) {
	var rent_order RentOrder
	err := db.Model(&RentOrder{}).Debug().
		Where("id = ?", id).
		First(&rent_order).Error
	if err != nil {
		return nil, e.FResult_ERROR
	} else {
		return &rent_order, e.FResult_OK
	}
}
